//********************************************************************
//	created:	12:10:2011   15:23
//	filename: 	xmlparser.cpp
//	author:		tiamo
//	purpose:	xml parser
//********************************************************************

#include "stdafx.h"
#include "xmlparser.h"

namespace utils
{
	//
	// reset
	//
	void XmlParser::reset()
	{
		mDocumentContent.reset(true);

		if(mParserContext)
		{
			if(mParserContext->myDoc)
				xmlFreeDoc(mParserContext->myDoc);

			xmlFreeParserCtxt(mParserContext);
		}

		mParserContext														= nullptr;
	}

	//
	// parse
	//
	void XmlParser::parse(DataBuffer const& documentContent, bool htmlMode)
	{
		reset();
		mDocumentContent													= documentContent;
		if(htmlMode)
			parseHtml();
		else
			parseXml();
	}

	//
	// parse
	//
	void XmlParser::parse(DataBuffer&& documentContent, bool htmlMode)
	{
		reset();
		mDocumentContent													= std::move(documentContent);
		if(htmlMode)
			parseHtml();
		else
			parseXml();
	}

	//
	// get nodes set
	//
	std::vector<xmlNodePtr> XmlParser::getNodes(wchar_t const* nodesPath, xmlNodePtr startNode)
	{
		if(!mParserContext || !mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): invalid parser context", this);

		xmlXPathContextPtr xpathContext										= xmlXPathNewContext(mParserContext->myDoc);
		ON_BLOCK_EXIT([xpathContext](){xmlXPathFreeContext(xpathContext);});
		if(startNode)
			xpathContext->node												= startNode;

		xmlXPathObjectPtr xpathObject										= xmlXPathEvalExpression(reinterpret_cast<xmlChar const*>(wideToUtf8(nodesPath).c_str()), xpathContext);
		if(!xpathObject)
			ThrowException(L"XmlParser(%p): eval xpath expr(%ls) failed (%ls)", this, nodesPath, utf8ToWide(xmlGetLastError()->message).c_str());
		ON_BLOCK_EXIT([xpathObject](){xmlXPathFreeObject(xpathObject);});

		if(xpathObject->type != XPATH_NODESET || !xpathObject->nodesetval)
			ThrowException(L"XmlParser(%p): invalid xpath(%ls) result (%u != nodes_set)", this, nodesPath, static_cast<uint32_t>(xpathObject->type));

		std::vector<xmlNodePtr> retValue;
		retValue.assign(xpathObject->nodesetval->nodeTab, xpathObject->nodesetval->nodeTab + xpathObject->nodesetval->nodeNr);
		return retValue;
	}

	//
	// get node
	//
	xmlNodePtr XmlParser::getNode(wchar_t const* nodePath, xmlNodePtr startNode)
	{
		if(!mParserContext || !mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): invalid parser context", this);

		xmlXPathContextPtr xpathContext										= xmlXPathNewContext(mParserContext->myDoc);
		ON_BLOCK_EXIT([xpathContext](){xmlXPathFreeContext(xpathContext);});
		if(startNode)
			xpathContext->node												= startNode;

		xmlXPathObjectPtr xpathObject										= xmlXPathEvalExpression(reinterpret_cast<xmlChar const*>(wideToUtf8(nodePath).c_str()), xpathContext);
		if(!xpathObject)
			ThrowException(L"XmlParser(%p): eval xpath expr(%ls) failed (%ls)", this, nodePath, utf8ToWide(xmlGetLastError()->message).c_str());
		ON_BLOCK_EXIT([xpathObject](){xmlXPathFreeObject(xpathObject);});

		if(xpathObject->type != XPATH_NODESET || !xpathObject->nodesetval || !xpathObject->nodesetval->nodeNr)
			ThrowException(L"XmlParser(%p): invalid xpath(%ls) result (%u != nodes_set)", this, nodePath, static_cast<uint32_t>(xpathObject->type));

		return xpathObject->nodesetval->nodeTab[0];
	}

	//
	// get string value
	//
	UniString XmlParser::getString(wchar_t const* valuePath, xmlNodePtr startNode, bool* checkExist)
	{
		if(!mParserContext || !mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): invalid parser context", this);

		xmlXPathContextPtr xpathContext										= xmlXPathNewContext(mParserContext->myDoc);
		ON_BLOCK_EXIT([xpathContext](){xmlXPathFreeContext(xpathContext);});
		if(startNode)
			xpathContext->node												= startNode;

		xmlXPathObjectPtr xpathObject										= xmlXPathEvalExpression(reinterpret_cast<xmlChar const*>(wideToUtf8(valuePath).c_str()), xpathContext);
		if(!xpathObject)
			ThrowException(L"XmlParser(%p): eval xpath expr(%ls) failed (%ls) ", this, valuePath, utf8ToWide(xmlGetLastError()->message).c_str());
		ON_BLOCK_EXIT([xpathObject](){xmlXPathFreeObject(xpathObject);});

		if(xpathObject->type != XPATH_NODESET || !xpathObject->nodesetval || !xpathObject->nodesetval->nodeNr)
		{
			if(!checkExist)
				ThrowException(L"XmlParser(%p): invalid xpath(%ls) result (%u != nodes_set)", this, valuePath, static_cast<uint32_t>(xpathObject->type));
			else
				*checkExist													= false;

			return L"";
		}
		else if(checkExist)
		{
			*checkExist														= true;
		}

		xmlChar* stringValue												= xmlXPathCastNodeSetToString(xpathObject->nodesetval);
		ON_BLOCK_EXIT([stringValue](){xmlFree(stringValue);});
		return utf8ToWide(reinterpret_cast<char const*>(stringValue));
	}

	//
	// get number
	//
	double XmlParser::getDouble(wchar_t const* valuePath, xmlNodePtr startNode, bool* checkExist)
	{
		if(!mParserContext || !mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): invalid parser context", this);

		xmlXPathContextPtr xpathContext										= xmlXPathNewContext(mParserContext->myDoc);
		ON_BLOCK_EXIT([xpathContext](){xmlXPathFreeContext(xpathContext);});
		if(startNode)
			xpathContext->node												= startNode;

		xmlXPathObjectPtr xpathObject										= xmlXPathEvalExpression(reinterpret_cast<xmlChar const*>(wideToUtf8(valuePath).c_str()), xpathContext);
		if(!xpathObject)
			ThrowException(L"XmlParser(%p): eval xpath expr(%ls) failed (%ls) ", this, valuePath, utf8ToWide(xmlGetLastError()->message).c_str());
		ON_BLOCK_EXIT([xpathObject](){xmlXPathFreeObject(xpathObject);});

		if(xpathObject->type != XPATH_NODESET || !xpathObject->nodesetval || !xpathObject->nodesetval->nodeNr)
		{
			if(!checkExist)
				ThrowException(L"XmlParser(%p): invalid xpath(%ls) result (%u != nodes_set)", this, valuePath, static_cast<uint32_t>(xpathObject->type));
			else
				*checkExist													= false;

			return 0;
		}
		else if(checkExist)
		{
			*checkExist														= true;
		}

		return xmlXPathCastNodeSetToNumber(xpathObject->nodesetval);
	}

	//
	// get attribute
	//
	UniString XmlParser::getAttribute(xmlNodePtr xmlNode, wchar_t const* attributeName)
	{
		if(!xmlNode)
			ThrowException(L"XmlParser(%p): invalid node", this);

		if(!mParserContext || !mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): invalid parser context", this);

		Utf8String rawAttributeName											= wideToUtf8(attributeName);
		if(!xmlHasProp(xmlNode, BAD_CAST(rawAttributeName.c_str())))
			ThrowException(L"XmlParser(%p): node(%p) does not has attribute (%ls)", this, xmlNode, attributeName);

		xmlChar* rawAttribute												= xmlGetProp(xmlNode, BAD_CAST(rawAttributeName.c_str()));
		ON_BLOCK_EXIT([rawAttribute](){xmlFree(rawAttribute);});
		return utf8ToWide(reinterpret_cast<char const*>(rawAttribute));
	}

	//
	// get content
	//
	UniString XmlParser::getContent(xmlNodePtr xmlNode)
	{
		if(!xmlNode)
			ThrowException(L"XmlParser(%p): invalid node", this);

		if(!mParserContext || !mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): invalid parser context", this);

		xmlChar* rawContent													= xmlNodeGetContent(xmlNode);
		ON_BLOCK_EXIT([rawContent](){xmlFree(rawContent);});
		return utf8ToWide(reinterpret_cast<char const*>(rawContent));
	}

	//
	// parse xml
	//
	void XmlParser::parseXml()
	{
		if(!mDocumentContent.getLength())
			ThrowException(L"XmlParser(%p): unable to parse empty document", this);

		if(*(static_cast<uint8_t*>(mDocumentContent.getBuffer()) + mDocumentContent.getLength() - 1))
		{
			if(mDocumentContent.isReadOnly() || !mDocumentContent.getLeftSpace())
			{
				DataBuffer tempBuffer(mDocumentContent.getLength() + sizeof(uint8_t));
				tempBuffer.appendData(mDocumentContent.getBuffer(), mDocumentContent.getLength());
				mDocumentContent											= std::move(tempBuffer);
			}
		
			mDocumentContent.appendData<uint8_t>(0);
		}

		mParserContext														= xmlCreateDocParserCtxt(static_cast<xmlChar const*>(mDocumentContent.getBuffer()));
		if(!mParserContext)
			ThrowException(L"XmlParser(%p): unable to create parser context", this);

		if(xmlParseDocument(mParserContext) || !mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): unable to parse document (%ls)", this, utf8ToWide(xmlGetLastError()->message).c_str());
	}

	//
	// parse html
	//
	void XmlParser::parseHtml()
	{
		if(!mDocumentContent.getLength())
			ThrowException(L"XmlParser(%p): unable to parse empty document", this);

		mParserContext														= htmlCreateMemoryParserCtxt(static_cast<char const*>(mDocumentContent.getBuffer()), static_cast<int>(mDocumentContent.getLength()));
		if(!mParserContext)
			ThrowException(L"XmlParser(%p): unable to create parser context", this);

		if(htmlCtxtUseOptions(mParserContext, HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING))
			ThrowException(L"XmlParser(%p): unable to set parser options (%ls)", this, utf8ToWide(xmlGetLastError()->message).c_str());

	#if 0
		if(htmlParseDocument(mParserContext))
			ThrowException(L"HtmlParser(%p): unable to parse document (%ls)", this, utf8ToWide(xmlGetLastError()->message).c_str());
	#else
		htmlParseDocument(mParserContext);
		if(!mParserContext->myDoc)
			ThrowException(L"XmlParser(%p): unable to parse document (%ls)", this, utf8ToWide(xmlGetLastError()->message).c_str());
	#endif
	}
}
